import tkinter as tk
from tkinter import filedialog, messagebox
import pandas as pd
import numpy as np
from scipy.spatial import cKDTree
from scipy.interpolate import griddata
from scipy.spatial.distance import cdist
# 添加matplotlib相关导入
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure


class ContourVolumeCalculator:

    def __init__(self, root):
        self.root = root
        self.root.title("等高线体积计算器")
        self.root.geometry("400x400")

        self.old_file_path = None
        self.new_file_path = None

        # 设置中文字体支持
        plt.rcParams['font.sans-serif'] = [
            'SimHei', 'FangSong', 'Arial Unicode MS'
        ]
        plt.rcParams['axes.unicode_minus'] = False

        # 创建界面元素
        self.create_widgets()

    def create_widgets(self):
        # 标题标签
        title_label = tk.Label(self.root,
                               text="等高线体积差异计算器",
                               font=("Arial", 16))
        title_label.pack(pady=10)

        # 按钮框架
        button_frame = tk.Frame(self.root)
        button_frame.pack(pady=10)

        # 选择旧文件按钮
        self.old_file_btn = tk.Button(button_frame,
                                      text="选择旧等高线文件 (old.csv)",
                                      command=self.select_old_file,
                                      width=25)
        self.old_file_btn.pack(pady=5)

        # 选择新文件按钮
        self.new_file_btn = tk.Button(button_frame,
                                      text="选择新等高线文件 (new.csv)",
                                      command=self.select_new_file,
                                      width=25)
        self.new_file_btn.pack(pady=5)

        # 计算体积差按钮
        self.calculate_btn = tk.Button(
            button_frame,
            text="计算重叠部分体积差",
            command=self.calculate_volume_difference,
            width=25,
            state=tk.DISABLED)
        self.calculate_btn.pack(pady=5)

        # 文件路径显示标签
        self.old_file_label = tk.Label(self.root, text="未选择旧文件", fg="gray")
        self.old_file_label.pack()

        self.new_file_label = tk.Label(self.root, text="未选择新文件", fg="gray")
        self.new_file_label.pack()

    def select_old_file(self):
        file_path = filedialog.askopenfilename(title="选择旧等高线文件",
                                               filetypes=[
                                                   ("CSV files", "*.csv"),
                                                   ("All files", "*.*")
                                               ])
        if file_path:
            self.old_file_path = file_path
            self.old_file_label.config(text=f"旧文件: {file_path.split('/')[-1]}",
                                       fg="black")
            self.check_enable_calculate()

    def select_new_file(self):
        file_path = filedialog.askopenfilename(title="选择新等高线文件",
                                               filetypes=[
                                                   ("CSV files", "*.csv"),
                                                   ("All files", "*.*")
                                               ])
        if file_path:
            self.new_file_path = file_path
            self.new_file_label.config(text=f"新文件: {file_path.split('/')[-1]}",
                                       fg="black")
            self.check_enable_calculate()

    def check_enable_calculate(self):
        if self.old_file_path and self.new_file_path:
            self.calculate_btn.config(state=tk.NORMAL)
        else:
            self.calculate_btn.config(state=tk.DISABLED)

    def read_csv_data(self, file_path):
        try:
            df = pd.read_csv(file_path, encoding='utf-8')
        except UnicodeDecodeError:
            df = pd.read_csv(file_path, encoding='gbk')

        # 提取坐标和高程数据
        x = df['东坐标'].values
        y = df['北坐标'].values
        z = df['水底高程'].values

        return np.column_stack((x, y)), z

    def interpolate_with_mask(self,
                              points,
                              elevations,
                              Xi,
                              Yi,
                              distance_threshold=5):
        """
        使用与new.py相同的插值方法和遮罩算法
        """
        # 使用griddata进行插值，只在数据点凸包内插值，外部区域设为NaN
        grid_points = np.column_stack([Xi.ravel(), Yi.ravel()])
        Zi = griddata((points[:, 0], points[:, 1]),
                      elevations, (Xi, Yi),
                      method='cubic',
                      fill_value=np.nan)

        # 创建一个遮罩，隐藏距离数据点过远的区域
        # 计算每个网格点到最近数据点的距离
        distances = cdist(grid_points, points).min(axis=1)
        mask = distances.reshape(Xi.shape) > distance_threshold
        Zi[mask] = np.nan

        return Zi

    def calculate_volume_difference(self):
        try:
            # 读取数据
            old_points, old_elevations = self.read_csv_data(self.old_file_path)
            new_points, new_elevations = self.read_csv_data(self.new_file_path)

            # 创建边界框
            old_min_x, old_min_y = np.min(old_points, axis=0)
            old_max_x, old_max_y = np.max(old_points, axis=0)

            new_min_x, new_min_y = np.min(new_points, axis=0)
            new_max_x, new_max_y = np.max(new_points, axis=0)

            # 计算重叠区域
            overlap_min_x = max(old_min_x, new_min_x)
            overlap_max_x = min(old_max_x, new_max_x)
            overlap_min_y = max(old_min_y, new_min_y)
            overlap_max_y = min(old_max_y, new_max_y)

            if overlap_min_x >= overlap_max_x or overlap_min_y >= overlap_max_y:
                messagebox.showerror("错误", "两个数据集没有重叠区域")
                return

            # 在重叠区域内创建更密集的规则网格（与new.py一致）
            xi = np.linspace(overlap_min_x, overlap_max_x, 200)
            yi = np.linspace(overlap_min_y, overlap_max_y, 200)
            Xi, Yi = np.meshgrid(xi, yi)

            # 使用与new.py相同的插值方法和遮罩算法
            old_Zi = self.interpolate_with_mask(old_points, old_elevations, Xi,
                                                Yi)
            new_Zi = self.interpolate_with_mask(new_points, new_elevations, Xi,
                                                Yi)

            # 移除NaN值
            valid_mask = ~(np.isnan(old_Zi) | np.isnan(new_Zi))
            old_Zi_valid = old_Zi[valid_mask]
            new_Zi_valid = new_Zi[valid_mask]

            if len(old_Zi_valid) == 0:
                messagebox.showerror("错误", "重叠区域内没有有效的插值数据")
                return

            # 计算网格单元面积
            cell_area = ((overlap_max_x - overlap_min_x) / (Xi.shape[1] - 1)) * \
                        ((overlap_max_y - overlap_min_y) / (Xi.shape[0] - 1))

            # 计算体积差
            volume_difference = np.sum(
                np.abs(new_Zi_valid - old_Zi_valid)) * cell_area

            # 分别计算正差值和负差值的体积
            diff_values = new_Zi_valid - old_Zi_valid
            positive_volume = np.sum(diff_values[diff_values > 0]) * cell_area
            negative_volume = np.sum(diff_values[diff_values < 0]) * cell_area
            # 取绝对值显示负体积为正值
            negative_volume_abs = abs(negative_volume)

            # 显示结果
            result_message = (
                f"重叠区域体积差: {volume_difference:.2f} 立方米\n"
                f"正差值体积(新增): {positive_volume:.2f} 立方米\n"
                f"负差值体积(减少): {negative_volume_abs:.2f} 立方米\n"
                f"重叠区域面积: {((overlap_max_x - overlap_min_x) * (overlap_max_y - overlap_min_y)):.2f} 平方米\n\n"
            )
            messagebox.showinfo("计算结果", result_message)

            # 显示重叠区域比较图
            self.show_overlap_comparison(Xi, Yi, old_Zi, new_Zi, overlap_min_x,
                                         overlap_max_x, overlap_min_y,
                                         overlap_max_y)

        except Exception as e:
            messagebox.showerror("错误", f"计算过程中发生错误:\n{str(e)}")

    def show_overlap_comparison(self, Xi, Yi, old_Zi, new_Zi, overlap_min_x,
                                overlap_max_x, overlap_min_y, overlap_max_y):
        # 创建新窗口显示重叠区域比较图
        comparison_window = tk.Toplevel(self.root)
        comparison_window.title("重叠区域比较图")
        comparison_window.geometry("1200x600")

        # 创建matplotlib图形
        fig = Figure(figsize=(15, 6), dpi=100)

        # 创建从深蓝色到黄色的渐变色映射（与new.py一致）
        colors = ['darkblue', 'blue', 'cyan', 'green', 'yellow']
        n_bins = 20  # 与等高线级别数一致
        cmap = plt.cm.colors.LinearSegmentedColormap.from_list('custom',
                                                               colors,
                                                               N=n_bins)

        # 读取原始数据点用于绘制连线
        old_points, old_elevations = self.read_csv_data(self.old_file_path)
        new_points, new_elevations = self.read_csv_data(self.new_file_path)

        # 计算统一的颜色范围
        all_values = np.concatenate([old_Zi.flatten(), new_Zi.flatten()])
        valid_values = all_values[~np.isnan(all_values)]
        vmin, vmax = valid_values.min(), valid_values.max()

        # 绘制旧数据等高线图
        ax1 = fig.add_subplot(131)
        # 绘制填充等高线图
        contour_filled1 = ax1.contourf(Xi,
                                       Yi,
                                       old_Zi,
                                       levels=20,
                                       cmap=cmap,
                                       vmin=vmin,
                                       vmax=vmax)
        # 绘制等高线
        contour_lines1 = ax1.contour(Xi,
                                     Yi,
                                     old_Zi,
                                     levels=20,
                                     colors='black',
                                     linewidths=0.8,
                                     alpha=0.9)
        ax1.clabel(contour_lines1, inline=True, fontsize=8, fmt='%.1f')
        # 用白色虚线连接原始数据点
        ax1.plot(old_points[:, 0],
                 old_points[:, 1],
                 color='white',
                 linestyle='--',
                 linewidth=0.5,
                 alpha=0.7,
                 label='原始数据连线')
        ax1.set_title('旧等高线数据')
        ax1.set_xlabel('东坐标')
        ax1.set_ylabel('北坐标')
        ax1.set_xlim(overlap_min_x, overlap_max_x)
        ax1.set_ylim(overlap_min_y, overlap_max_y)
        fig.colorbar(contour_filled1, ax=ax1, label='高程 (m)')

        # 绘制新数据等高线图
        ax2 = fig.add_subplot(132)
        # 绘制填充等高线图
        contour_filled2 = ax2.contourf(Xi,
                                       Yi,
                                       new_Zi,
                                       levels=20,
                                       cmap=cmap,
                                       vmin=vmin,
                                       vmax=vmax)
        # 绘制等高线
        contour_lines2 = ax2.contour(Xi,
                                     Yi,
                                     new_Zi,
                                     levels=20,
                                     colors='black',
                                     linewidths=0.8,
                                     alpha=0.9)
        ax2.clabel(contour_lines2, inline=True, fontsize=8, fmt='%.1f')
        # 用白色虚线连接原始数据点
        ax2.plot(new_points[:, 0],
                 new_points[:, 1],
                 color='white',
                 linestyle='--',
                 linewidth=0.5,
                 alpha=0.7,
                 label='原始数据连线')
        ax2.set_title('新等高线数据')
        ax2.set_xlabel('东坐标')
        ax2.set_ylabel('北坐标')
        ax2.set_xlim(overlap_min_x, overlap_max_x)
        ax2.set_ylim(overlap_min_y, overlap_max_y)
        fig.colorbar(contour_filled2, ax=ax2, label='高程 (m)')

        # 绘制高差等高线图
        ax3 = fig.add_subplot(133)
        # 计算高差
        diff_Zi = new_Zi - old_Zi
        # 创建红蓝渐变色映射表示高差
        diff_cmap = plt.cm.RdBu
        # 绘制高差填充等高线图
        diff_contour_filled = ax3.contourf(Xi,
                                           Yi,
                                           diff_Zi,
                                           levels=20,
                                           cmap=diff_cmap)
        # 绘制高差等高线
        diff_contour_lines = ax3.contour(Xi,
                                         Yi,
                                         diff_Zi,
                                         levels=20,
                                         colors='black',
                                         linewidths=0.8,
                                         alpha=0.9)
        ax3.clabel(diff_contour_lines, inline=True, fontsize=8, fmt='%.1f')
        ax3.set_title('高差等高线图 (新-旧)')
        ax3.set_xlabel('东坐标')
        ax3.set_ylabel('北坐标')
        ax3.set_xlim(overlap_min_x, overlap_max_x)
        ax3.set_ylim(overlap_min_y, overlap_max_y)
        fig.colorbar(diff_contour_filled, ax=ax3, label='高程差 (m)')

        # 在tkinter窗口中显示图形
        canvas = FigureCanvasTkAgg(fig, master=comparison_window)
        canvas.draw()
        canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)


# 添加可供外部调用的函数
def read_csv_data(file_path):
    """
    读取CSV文件数据
    
    Args:
        file_path (str): CSV文件路径
        
    Returns:
        tuple: (points, elevations) 其中points是坐标点数组，elevations是高程数组
    """
    try:
        df = pd.read_csv(file_path, encoding='utf-8')
    except UnicodeDecodeError:
        df = pd.read_csv(file_path, encoding='gbk')

    # 提取坐标和高程数据
    x = df['东坐标'].values
    y = df['北坐标'].values
    z = df['水底高程'].values

    return np.column_stack((x, y)), z


def interpolate_with_mask(points, elevations, Xi, Yi, distance_threshold=5):
    """
    使用与new.py相同的插值方法和遮罩算法
    
    Args:
        points (array): 原始数据点坐标
        elevations (array): 原始数据点高程
        Xi, Yi (array): 插值网格
        distance_threshold (float): 距离阈值
        
    Returns:
        array: 插值结果
    """
    # 使用griddata进行插值，只在数据点凸包内插值，外部区域设为NaN
    grid_points = np.column_stack([Xi.ravel(), Yi.ravel()])
    Zi = griddata((points[:, 0], points[:, 1]),
                  elevations, (Xi, Yi),
                  method='cubic',
                  fill_value=np.nan)

    # 创建一个遮罩，隐藏距离数据点过远的区域
    # 计算每个网格点到最近数据点的距离
    distances = cdist(grid_points, points).min(axis=1)
    mask = distances.reshape(Xi.shape) > distance_threshold
    Zi[mask] = np.nan

    return Zi


def calculate_volume_difference_from_files(old_file_path, new_file_path):
    """
    从文件路径计算两个等高线数据的体积差
    
    Args:
        old_file_path (str): 旧等高线文件路径
        new_file_path (str): 新等高线文件路径
        
    Returns:
        dict: 包含计算结果的字典
    """
    try:
        # 读取数据
        old_points, old_elevations = read_csv_data(old_file_path)
        new_points, new_elevations = read_csv_data(new_file_path)

        # 创建边界框
        old_min_x, old_min_y = np.min(old_points, axis=0)
        old_max_x, old_max_y = np.max(old_points, axis=0)

        new_min_x, new_min_y = np.min(new_points, axis=0)
        new_max_x, new_max_y = np.max(new_points, axis=0)

        # 计算重叠区域
        overlap_min_x = max(old_min_x, new_min_x)
        overlap_max_x = min(old_max_x, new_max_x)
        overlap_min_y = max(old_min_y, new_min_y)
        overlap_max_y = min(old_max_y, new_max_y)

        if overlap_min_x >= overlap_max_x or overlap_min_y >= overlap_max_y:
            raise ValueError("两个数据集没有重叠区域")

        # 在重叠区域内创建更密集的规则网格（与new.py一致）
        xi = np.linspace(overlap_min_x, overlap_max_x, 200)
        yi = np.linspace(overlap_min_y, overlap_max_y, 200)
        Xi, Yi = np.meshgrid(xi, yi)

        # 使用与new.py相同的插值方法和遮罩算法
        old_Zi = interpolate_with_mask(old_points, old_elevations, Xi, Yi)
        new_Zi = interpolate_with_mask(new_points, new_elevations, Xi, Yi)

        # 移除NaN值
        valid_mask = ~(np.isnan(old_Zi) | np.isnan(new_Zi))
        old_Zi_valid = old_Zi[valid_mask]
        new_Zi_valid = new_Zi[valid_mask]

        if len(old_Zi_valid) == 0:
            raise ValueError("重叠区域内没有有效的插值数据")

        # 计算网格单元面积
        cell_area = ((overlap_max_x - overlap_min_x) / (Xi.shape[1] - 1)) * \
                    ((overlap_max_y - overlap_min_y) / (Xi.shape[0] - 1))

        # 计算体积差
        volume_difference = np.sum(
            np.abs(new_Zi_valid - old_Zi_valid)) * cell_area

        # 分别计算正差值和负差值的体积
        diff_values = new_Zi_valid - old_Zi_valid
        positive_volume = np.sum(diff_values[diff_values > 0]) * cell_area
        negative_volume = np.sum(diff_values[diff_values < 0]) * cell_area
        # 取绝对值显示负体积为正值
        negative_volume_abs = abs(negative_volume)

        return {
            "total_volume_diff":
            volume_difference,
            "positive_volume":
            positive_volume,
            "negative_volume":
            negative_volume_abs,
            "overlap_area":
            (overlap_max_x - overlap_min_x) * (overlap_max_y - overlap_min_y),
            "Xi":
            Xi,
            "Yi":
            Yi,
            "old_Zi":
            old_Zi,
            "new_Zi":
            new_Zi,
            "old_points":
            old_points,
            "new_points":
            new_points,
            "overlap_bounds": {
                "min_x": overlap_min_x,
                "max_x": overlap_max_x,
                "min_y": overlap_min_y,
                "max_y": overlap_max_y
            }
        }
    except Exception as e:
        raise Exception(f"计算过程中发生错误: {str(e)}")


if __name__ == "__main__":
    root = tk.Tk()
    app = ContourVolumeCalculator(root)
    root.mainloop()
